package cn.net.susan.factory;

import cn.net.susan.entity.RecommendEntity;
import cn.net.susan.enums.RecommenderSimilarityEnum;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.mahout.cf.taste.common.Refreshable;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.eval.RecommenderBuilder;
import org.apache.mahout.cf.taste.eval.RecommenderEvaluator;
import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
import org.apache.mahout.cf.taste.impl.eval.AverageAbsoluteDifferenceRecommenderEvaluator;
import org.apache.mahout.cf.taste.impl.eval.RMSRecommenderEvaluator;
import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
import org.apache.mahout.cf.taste.impl.model.GenericPreference;
import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.neighborhood.ThresholdUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericBooleanPrefItemBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericBooleanPrefUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.CityBlockSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.TanimotoCoefficientSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.model.Preference;
import org.apache.mahout.cf.taste.model.PreferenceArray;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 推荐工厂
 *
 * @author 苏三
 * @date 2024/10/16 下午3:40
 */
@Slf4j
public class RecommendFactory {


    /**
     * 创建模型数据
     *
     * @param recommendList 数据集合
     * @return 模型数据
     */
    public static DataModel buildJdbcDataModel(List<? extends RecommendEntity> recommendList) {
        FastByIDMap<PreferenceArray> fastByIdMap = new FastByIDMap<>();
        Map<Long, List<Preference>> map = Maps.newHashMap();
        for (RecommendEntity recommendEntity : recommendList) {
            List<Preference> preferenceList = new ArrayList<Preference>();
            preferenceList.add(new GenericPreference(recommendEntity.getUserId(), recommendEntity.getItemId(), recommendEntity.getValue()));
            if (map.containsKey(recommendEntity.getUserId())) {
                List<Preference> preferenceListTemp = map.get(recommendEntity.getUserId());
                preferenceListTemp.addAll(preferenceList);
                map.put(recommendEntity.getUserId(), preferenceListTemp);
            } else {
                map.put(recommendEntity.getUserId(), preferenceList);
            }
        }
        for (Long key : map.keySet()) {
            List<Preference> preList = map.get(key);
            fastByIdMap.put(key, new GenericUserPreferenceArray(preList));
        }
        return new GenericDataModel(fastByIdMap);
    }


    public static UserSimilarity userSimilarity(RecommenderSimilarityEnum type, DataModel m) throws TasteException {
        return (UserSimilarity) getSimilarity(type, m);
    }

    public static ItemSimilarity itemSimilarity(RecommenderSimilarityEnum type, DataModel m) throws TasteException {
        return (ItemSimilarity) getSimilarity(type, m);
    }

    private static Refreshable getSimilarity(RecommenderSimilarityEnum type, DataModel m) throws TasteException {
        switch (type) {
            case SIMILARITY_PEARSON:
                return new PearsonCorrelationSimilarity(m);
            case SIMILARITY_COSINE:
                return new UncenteredCosineSimilarity(m);
            case SIMILARITY_TANIMOTO:
                return new TanimotoCoefficientSimilarity(m);
            case SIMILARITY_LOGLIKELIHOOD:
                return new LogLikelihoodSimilarity(m);
            case SIMILARITY_CITY_BLOCK:
                return new CityBlockSimilarity(m);
            case SIMILARITY_EUCLIDEAN:
            default:
                return new EuclideanDistanceSimilarity(m);
        }
    }

    /**
     * 构造近邻算法模型
     */
    public enum NEIGHBORHOOD {
        /**
         * 基于固定数量的邻居,对每个用户取固定数量N个最近邻居
         */
        NEAREST,
        /**
         * 基于相似度,对每个用户基于一定的限制，取落在相似度限制以内的所有用户为邻居
         */
        THRESHOLD
    }

    /**
     * 计算最近邻域
     *
     * @param type 算法类型
     * @param s    相似度算法
     * @param m    数据模型
     * @param num  NEAREST - 固定邻居数量 THRESHOLD -最低相似度
     * @return UserNeighborhood
     */
    public static UserNeighborhood userNeighborhood(NEIGHBORHOOD type, UserSimilarity s, DataModel m, double num) throws TasteException {
        switch (type) {
            case NEAREST:
                return new NearestNUserNeighborhood((int) num, s, m);
            case THRESHOLD:
            default:
                return new ThresholdUserNeighborhood(num, s, m);
        }
    }

    /**
     * 获取基于用户的推荐器
     *
     * @param us   相似度算法
     * @param un   最近邻算法
     * @param pref 是否需要首选,即偏好
     * @return RecommenderBuilder
     */
    public static RecommenderBuilder userRecommender(final UserSimilarity us, final UserNeighborhood un, boolean pref) {
        return pref
                //基于用户的推荐引擎
                ? model -> new GenericUserBasedRecommender(model, un, us)
                //基于用户的无偏好值推荐引擎
                : model -> new GenericBooleanPrefUserBasedRecommender(model, un, us);
    }

    /**
     * 获取基于用户的推荐器
     *
     * @param is   相似度算法
     * @param pref 是否需要首选
     * @return RecommenderBuilder
     */
    public static RecommenderBuilder itemRecommender(final ItemSimilarity is, boolean pref) {
        return pref
                //基于物品的推荐引擎
                ? model -> new GenericItemBasedRecommender(model, is)
                //基于物品的无偏好值推荐引擎
                : model -> new GenericBooleanPrefItemBasedRecommender(model, is);
    }

    /**
     * 构造算法评估模型
     */
    public enum EVALUATOR {
        /**
         * 计算平均差值
         */
        AVERAGE_ABSOLUTE_DIFFERENCE,

        /**
         * 计算用户的预测评分和实际评分之间的“均方根”差异。这是这个差值的平均值的平方根
         */
        RMS
    }

    public static RecommenderEvaluator buildEvaluator(EVALUATOR type) {
        switch (type) {
            case RMS:
                return new RMSRecommenderEvaluator();
            case AVERAGE_ABSOLUTE_DIFFERENCE:
            default:
                return new AverageAbsoluteDifferenceRecommenderEvaluator();
        }
    }
}
